import type { SourceCodeTransformer } from '@unocss/core';
import { expandVariantGroup } from '@unocss/core';

// 设计图宽度，一般是 px 或者 pt 单位
const viewportWidth = 375;
// 可转换的最小像素值
const minPx = 1;
// 转换后保留的小数位数
const unitPrecision = 6;

const classPrefix = 'uno-';

function toFixed(number: number, precision: number) {
  const multiplier = Math.pow(10, precision + 1);
  const wholeNumber = Math.floor(number * multiplier);

  return Math.round(wholeNumber / 10) * 10 / multiplier;
}

function getHash(str: string) {
  let i;
  let l;
  let hval = 0x811C9DC5;

  for (i = 0, l = str.length; i < l; i++) {
    hval ^= str.charCodeAt(i);
    hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
  }

  return (`00000${(hval >>> 0).toString(36)}`).slice(-6);
}

export function transformerViewport(): SourceCodeTransformer {
  const regex = new RegExp('(["\'`]):(mobile|uno):\\s([^\\1]*?)\\1', 'g');

  return {
    name: 'unocss-transformer-viewport-with-compile-class',
    enforce: 'pre',
    async transform(s, _, { uno, tokens }) {
      const matches = [...s.original.matchAll(regex)];

      if (!matches.length) {
        return;
      }

      for (const match of matches) {
        let body = expandVariantGroup(match[3].trim());
        const start = match.index;
        const replacements = [];

        if (match[2] === 'mobile') {
          body = body.replace(/(\d+)(?:px)?(?:\s|$)/g, (match, p1) => {
            const px = Number(p1);

            if (px <= minPx) {
              return match;
            }
            else {
              return `${toFixed(px / viewportWidth * 100, unitPrecision)}vw`;
            }
          });

          // 大写 PX 单位不会转成 vw 并且会转换回小写 px
          // 用于临时不想转 vw 时使用
          body = body.replace(/(\d+)PX/g, '$1px');
        }

        if (process.env.NODE_ENV === 'production') {
          // 只有生产模式下才进行 class 编译
          // 因为开发模式下貌似会影响热更新，效果不太好
          const result = await Promise.all(body.split(/\s+/).filter(Boolean).map(async (i) => [i, !!await uno.parseToken(i)]));
          const known = result.filter(([, matched]) => matched).map(([i]) => i);
          const unknown = result.filter(([, matched]) => !matched).map(([i]) => i);

          replacements.push(...unknown);
          body = known.join(' ');

          if (body) {
            body = body.split(/\s+/).sort().join(' ');

            const hash = getHash(body);
            const className = `${classPrefix}${hash}`;

            replacements.unshift(className);
            uno.config.shortcuts.push([className, body]);

            tokens.add(className);
          }

          s.overwrite(start + 1, start + match[0].length - 1, replacements.join(' '));
        }
        else {
          s.overwrite(start + 1, start + match[0].length - 1, body);
        }
      }
    },
  }
}
